import { computed, reactive, ref, Ref, toRef } from 'vue'
import config from '@/config'
import { Widget } from '@/models'
import { PageData } from '@/types'
import useDocState from './useDocState'
import useZoom from './useZoom'
import useLayout from './useLayout'
import { awaitTimeout, ScrollSmooth } from '@/utils'
import useScroll from '@/utils/compositions/useScroll'
import useScrollState from '@/utils/compositions/useScrollState'

/** 合同文档滚动 */
export default function useDocScroll({
  state,
  scrollState,
  zoom,
  layout,
  widgets
}: {
  state: ReturnType<typeof useDocState>
  scrollState: ReturnType<typeof useScrollState>
  zoom: ReturnType<typeof useZoom>
  layout: ReturnType<typeof useLayout>
  widgets: Ref<Widget[]>
}) {
  let syncTimer = 0

  const { virtualScrollThreshold } = config

  /** 是否锁定滚动同步 */
  const syncLocked = ref(false)

  /** 滚动节点 */
  const scroller = toRef(scrollState, 'scroller')

  const scroll = useScroll(scroller)

  /** 获取页面顶部距离 */
  function getPageTop(index: number | PageData) {
    const page = typeof index === 'number' ? state.pages[index] : index

    if (!page) return 0

    return (state.cumulativeHeights[page.p] - page.h) * zoom.level + (layout.pageSpacing ?? 0) * page.p
  }

  /** 检测页面是否在视图内 */
  function isPageInView(page: PageData, viewTop: number, viewBottom: number) {
    if (!page) return false

    const top = getPageTop(page)
    const bottom = top + page.h * zoom.level
    const spacing = config.virtualScrollSpacing

    return bottom + spacing >= viewTop && top - spacing <= viewBottom
  }

  /** 滚动到页面 */
  function scrollToPage(index: number, smooth: ScrollSmooth = 'auto') {
    scroll.scrollTo(getPageTop(index), null, smooth)
  }

  /**
   * 获取当前页面
   * 如果页面顶部在滚动视图上面 1/2 区域内，则视为当前页
   */
  function getCurrentPage() {
    const { ratio, scrollTop, height } = scrollState
    const { pages } = state
    const { length: pageLength } = pages

    if (pageLength < 2) return 0

    const max = scrollTop + height / 2
    const pageSpacing = layout.pageSpacing ?? 0
    let pageIndex = Math.floor((pageLength - 1) * ratio)
    let matched = false

    while (!matched) {
      const page = pages[pageIndex]

      if (!page) return 0

      const top = getPageTop(page)
      const bottom = top + page.h * zoom.level + pageSpacing

      if (top > max) {
        pageIndex--
      } else if (bottom < max) {
        pageIndex++
      } else {
        matched = true
      }
    }

    const page = pages[pageIndex]

    return page?.p || 0
  }

  /** 页面索引 */
  const pageIndex = computed({
    set: (index: number) => scrollToPage(index),
    get: getCurrentPage
  })

  return reactive({
    state: scrollState,

    ...scroll,

    /** 页面索引 */
    pageIndex,

    /** 可见的页面列表 */
    visiblePages: computed(() => {
      const { pages } = state
      const pageLength = pages.length
      const pIndex = pageIndex.value

      if (pageLength < virtualScrollThreshold) return pages

      const list: PageData[] = []
      const { scrollTop: top, height: scrollHeight } = scrollState
      const bottom = top + scrollHeight

      for (let i = pIndex; i >= 0; i--) {
        const page = pages[i]

        if (!page) break

        if (!isPageInView(page, top, bottom)) break

        list.push(page)
      }

      for (let i = pIndex + 1; i < pageLength; i++) {
        const page = pages[i]

        if (!page) break

        if (!isPageInView(page, top, bottom)) break

        list.push(page)
      }

      /** 过滤在视图区域内的页面 */
      return list
    }),

    /** 获取页面顶部距离 */
    getPageTop,

    /** 滚动到页面 */
    scrollToPage,

    /**
     * 滚动到控件
     * @param {string} id 控件 id
     * @param {number?} page 控件所在页面，骑缝或批量控件可能需要跳转对应页面
     */
    async scrollToWidget(id: string, page?: number) {
      const widget = widgets.value.find(widget => widget.id === id)
      const scrollEl = scroller.value

      if (!widget || !scrollEl) return

      // 跳转到文档
      state.docIndex = state.docs.findIndex(doc => doc.id === widget.docId)

      // 由于页面切换后会滚动到第一页，将控件定位滚动放到 setTimeout 后避免页面切换后定位失败
      await awaitTimeout()

      page = widget.isRiding || widget.isBatch ? page ?? widget.pages[0] : widget.p

      const pageData = state.pages[page]

      if (!pageData) return

      const pageW = (pageData.w + (layout.ridingActive ? layout.ridingWidth : 0)) * zoom.level
      const pageX = (scrollEl.scrollWidth - pageW) / 2

      // 定位到控件位置
      scroll.scrollTo(
        getPageTop(page) + widget.y * zoom.level - scrollEl.clientHeight * 0.2,
        pageX + (widget.isRiding ? pageW : widget.ox * zoom.level) - scrollEl.clientWidth / 2
      )
    },

    /** 是否锁定滚动同步 */
    syncLocked,

    /** 锁定同步滚动 */
    lockScrollSync() {
      syncLocked.value = true

      clearTimeout(syncTimer)

      syncTimer = window.setTimeout(() => {
        syncLocked.value = false
      }, 2000)
    }
  })
}
